﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Reflection;

namespace wottinystats
{



	public class ExtendedWebClient : WebClient
	{
		public int Timeout { get; set; }

		protected override WebRequest GetWebRequest(Uri address)
		{
			WebRequest request = base.GetWebRequest(address);
			if (request != null)
				request.Timeout = Timeout;
			return request;
		}

		public ExtendedWebClient()
		{
			Timeout = 100000; // the standard HTTP Request Timeout default
		}

		public ExtendedWebClient(int pTimeOut)
		{
			Timeout = pTimeOut;
		}
	}

	class Program
	{
		const string CURRENT_ANDROID_APP_TOKEN = "WG-WoT_Assistant-1.1.5";
		const string CURRENT_UNIVERSAL_TOKEN = "WG-WoT_Assistant-test";
        const string PROFILE_URL = "http://worldoftanks.ru/community/accounts/#uid#/api/1.2/?source_token="+CURRENT_UNIVERSAL_TOKEN;
        const string CLANPROFILE_URL = "http://worldoftanks.ru/community/clans/#cid#/api/1.0/?source_token="+CURRENT_UNIVERSAL_TOKEN;
        const string SEARCHPLAYER_URL = "http://worldoftanks.ru/community/accounts/api/1.2/?source_token="+CURRENT_UNIVERSAL_TOKEN+"&search=#search#&offset=#offset#&limit=100";
		private static string USER_VEHICLE_TEMPLATE = "#data.name#,#data.vehicles.name#,#data.vehicles.battle_count#,#data.vehicles.win_count#";
		private static string SEARCHUSERS_LINETEMPLATE = "#name#,#id#,#clan.name#,#clan.tag#,#clan.id#";
		private static string fnTemplate, sTemplate;
		private static List<string> knownCmdArgs = new List<string> { "help", "template", "fnin", "fnout", "clanid", "searchplayer", "tndomain", "dltn", "use_dltn", "tnfn" };
		private static Dictionary<string, string> dicArgs;
		private static Dictionary<string, string> dicTankNames = new Dictionary<string, string>();
		private static bool useTankNames = false;

		static void Main(string[] args)
		{
			string fnIn = "names.txt";
			string fnOut = "result.csv";
			string clanOut = "clanNames.txt";
			string TankNamesOut = "TankNames.txt";
			string searchUserOut = "searchUser.txt";
			System.IO.StreamWriter wlsw = null;


			//preparing web client used for downloading from wot api
			ExtendedWebClient wc = new ExtendedWebClient(1000);

			//replacing commas in templates to regional settings list separator char
			USER_VEHICLE_TEMPLATE = USER_VEHICLE_TEMPLATE.Replace(",", System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator);
			SEARCHUSERS_LINETEMPLATE = SEARCHUSERS_LINETEMPLATE.Replace(",", System.Globalization.CultureInfo.CurrentCulture.TextInfo.ListSeparator);


			showIntro();


			//checking passed args
			dicArgs = ParseArgs(args);

			if (dicArgs.ContainsKey("help")) showHelpAndExit();
			if (dicArgs.Count == 0)
			{
				Console.ForegroundColor = ConsoleColor.White;
				Console.WriteLine("use -help command to see detailed help\r\n");
				Console.ResetColor();
			}

			if (dicArgs.ContainsKey("fnin")) fnIn = dicArgs["fnin"];
			if (dicArgs.ContainsKey("fnout")) fnOut = dicArgs["fnout"];


			//trying to load template content
			fnTemplate = "";
			if (dicArgs.ContainsKey("template")) fnTemplate = dicArgs["template"];
			try
			{
				System.IO.StreamReader srtemplate = new System.IO.StreamReader(fnTemplate);
				sTemplate = srtemplate.ReadLine();
				Console.WriteLine("Loaded line template from " + fnTemplate);
			}
			catch (Exception e)
			{
				sTemplate = "";
			}
			finally
			{
				if (sTemplate.Trim().Length == 0) sTemplate = USER_VEHICLE_TEMPLATE;
			}

			//tank names processing
			if (dicArgs.ContainsKey("dltn") || dicArgs.ContainsKey("use_dltn"))
			{
				//downloading tank names
				dicTankNames = dicArgs.ContainsKey("tndomain") ? DownloadTankNames(wc, dicArgs["tndomain"]) : DownloadTankNames(wc);
				Console.WriteLine("Downloaded " + dicTankNames.Count.ToString()+" tank names from "+(dicArgs.ContainsKey("tndomain")?dicArgs["tndomain"]:"ru"+" domain"));

				if (dicArgs.ContainsKey("dltn"))
				{
					//saving names to file and exit
					if (dicArgs.ContainsKey("fnout")) TankNamesOut = fnOut;
					fnOut = TankNamesOut;
					//opening output
					wlsw = OpenFNOUT(fnOut);
					foreach (KeyValuePair<string,string> item in dicTankNames)
					{
						wlsw.WriteLine(item.Key + ":"+item.Value);
					}
					wlsw.Close();
					Console.WriteLine("Tank names saved to " + fnOut);
					Environment.Exit(0);
				}
				useTankNames = true;
			}
			//laoding tank names from file
			if (dicArgs.ContainsKey("tnfn"))
			{
				dicTankNames = LoadTankNamesFromFile(dicArgs["tnfn"]);
				Console.WriteLine("Loaded "+dicTankNames.Count.ToString()+" tank names from "+dicArgs["tnfn"]);
				useTankNames = true;
			}

			//loading clan user ids and saving it to output file
			if (dicArgs.ContainsKey("clanid") && dicArgs["clanid"] != "")
			{
				if (!dicArgs.ContainsKey("fnout")) fnOut = clanOut;

				//loading clan info json
				wc.Encoding = System.Text.Encoding.UTF8;

				string profile = "";
				//construct clan's profile page url and loadin it
				try
				{
					profile = wc.DownloadString(new Uri(CLANPROFILE_URL.Replace("#cid#", dicArgs["clanid"])));
				}
				catch (Exception e)
				{

					profile = "";
					Console.WriteLine("Unable to load clan info for clan#" + dicArgs["clanid"]);
					Environment.Exit(-5);
				}

				// deser. json
				JObject jo = (JObject)JsonConvert.DeserializeObject(profile);

				//opening output
				wlsw = OpenFNOUT(fnOut);

				Console.WriteLine("Clan#" + dicArgs["clanid"] + "#" + jo["data"]["abbreviation"] + " - " + jo["data"]["name"]);
				Console.WriteLine("Saving " + ((JArray)jo["data"]["members"]).Count.ToString() + " clan members to " + fnOut);

				//going trough all members and saving their id-name to output
				foreach (JObject joi in (JArray)jo["data"]["members"])
				{
					wlsw.WriteLine(joi["account_id"] + "-" + joi["account_name"]);
				}
				wlsw.Close();
				Environment.Exit(0);


			}

			//search users and saving to fnOut
			if (dicArgs.ContainsKey("searchplayer") && dicArgs["searchplayer"] != "")
			{
				//loading clan info json
				wc.Encoding = System.Text.Encoding.UTF8;

				int foundCnt = 0;
				int offset = 0;


				//if there was no template file name in cmd param then using default one
				if (fnTemplate.Length == 0) sTemplate = SEARCHUSERS_LINETEMPLATE;

				//opening output
				if (!dicArgs.ContainsKey("fnout")) fnOut = searchUserOut;
				wlsw = OpenFNOUT(fnOut);

				string results = "";
				do
				{
					//construct clan's profile page url and loadin it
					try
					{
						results = wc.DownloadString(new Uri(ReplacePlaceholders(SEARCHPLAYER_URL, new Dictionary<string, string>() { { "search", dicArgs["searchplayer"] }, { "offset", offset.ToString() } }, false)));
					}
					catch (Exception e)
					{

						results = "";
						Console.WriteLine("Unable to load search results for query:" + dicArgs["searchplayer"]);
						Environment.Exit(-6);
					}

					// deser. json
					JObject jo = (JObject)JsonConvert.DeserializeObject(results);

					foundCnt = ((JArray)jo["data"]["items"]).Count;

					Console.WriteLine("Saving " + foundCnt.ToString() + " result items to " + fnOut);

					string ss;
					foreach (JObject joi in jo["data"]["items"])
					{
						ss = GetParsedTemplateString(joi, sTemplate, false);
						wlsw.WriteLine(ss);
					};

					offset += foundCnt;

				} while (foundCnt == 100); //repeating until all players found

				Console.WriteLine("Total  " + offset.ToString() + " players found");

				wlsw.Close();
				Console.WriteLine("Done.");

				Environment.Exit(0);

			}

			Console.WriteLine("Loading user names from " + fnIn);
			Console.WriteLine("writing result to " + fnOut);

			int userID;
			string userName, userStatJSON, row, res;
			res = "";


			// now loading names and loading stats
			System.IO.StreamReader wlsr;
			try
			{
				wlsr = new System.IO.StreamReader(fnIn, System.Text.Encoding.UTF8);
			}
			catch (Exception e)
			{
				Console.WriteLine("Can't open " + fnIn);
				Console.WriteLine("Error info: " + e.Message);
				System.Environment.Exit(-1);
				return;
			}

			wlsw = OpenFNOUT(fnOut);


			string s = wlsr.ReadLine();
			while (s != null)
			{
				MatchCollection mc = Regex.Matches(s, @"(\d+)-(.*)");
				if (mc[0].Groups.Count != 3)
				{
					Console.WriteLine("Wrong WOT user info provided: " + s);
					Console.WriteLine("Correct format is ID-Name (i.e. 42897-SergeySch)");
				}
				userID = Convert.ToInt32(mc[0].Groups[1].Value);
				userName = mc[0].Groups[2].Value;

				row = sTemplate; // USER_VEHICLE_TEMPLATE;

				//loading stats
				Console.Write("Processing " + userName + " ... ");
				userStatJSON = DownloadUserStatJSON(userID, wc);
				JObject jo = (JObject)JsonConvert.DeserializeObject(userStatJSON);

				if (jo != null && jo["status"].ToString().ToLowerInvariant() == "ok")
				{
					foreach (JObject joi in (JArray)jo["data"]["vehicles"])
					{
						res = res + GetParsedTemplateString(joi, row) + "\r\n";
					}
					res = GetParsedTemplateString(jo, res); //filling non-tank related info
					wlsw.WriteLine(res.Trim());
					res = "";
					Console.WriteLine(" Done.");
				}
				else
				{
					Console.Write(" Failed.");
					if (jo != null)
					{
						Console.WriteLine(" Reason: " + jo["error"].ToString());
					}
					else
					{
						Console.WriteLine(" Reason: Unknown, probably network error");
					}
				}

				s = wlsr.ReadLine();
			}
			wlsr.Close();
			wlsw.Close();
			Environment.Exit(0);
		}


		private static Dictionary<string, string> ParseArgs(string[] args)
		{
			Dictionary<string, string> dicArgs = new Dictionary<string, string>();
			int i; string item, nextitem;
			for (i = 0; i < args.Length; i++)
			{
				item = args[i];
				nextitem = (i < (args.Length - 1) ? args[i + 1] : "");
				if (item.StartsWith("-"))
				{
					item = item.Substring(1).ToLowerInvariant();
					if (knownCmdArgs.IndexOf(item) >= 0)  //if arg is cmd name then add it to dictionary
					{
						// ignoring already added params
						if (!dicArgs.ContainsKey(item)) dicArgs.Add(item, nextitem);
					}
				}
			}
			return dicArgs;
		}

		private static string GetParsedTemplateString(JObject joi, string pTemplate, bool pKeepUnknownPlaceholders = true)
		{
			string placeholder, jsonPropertyName, pp, propvalue;
			string res = pTemplate;
			//loading all values placeholder names
			MatchCollection mc = Regex.Matches(pTemplate, @"\#(.*?)\#");
			foreach (Match m in mc)
			{
				placeholder = m.Groups[1].Value.ToLowerInvariant(); ;

				//if has data.vehicles in placeholder name then removing it and processing it as property of current tank record
				if (placeholder.StartsWith("data.vehicles"))
				{
					jsonPropertyName = placeholder.Replace("data.vehicles.", "");
					propvalue = joi[jsonPropertyName].ToString();
					
					//replacing tank names
					if (useTankNames && jsonPropertyName == "name")
					{
						if (dicTankNames.ContainsKey(propvalue.ToLowerInvariant()))
						{
							propvalue = dicTankNames[propvalue.ToLowerInvariant()].Trim();
						}
					}
					res = res.Replace("#" + placeholder + "#", propvalue);
				}
				else
				{
					Newtonsoft.Json.Linq.JObject j = joi;
					jsonPropertyName = "";

					//trying to load property values level by level
					pp = placeholder;
					int dotindex = pp.IndexOf(".");
					while (dotindex > -1)
					{
						jsonPropertyName = pp.Substring(0, dotindex);
						pp = pp.Remove(0, dotindex + 1);
						dotindex = pp.IndexOf(".");
						if (j[jsonPropertyName] != null && j[jsonPropertyName].HasValues)
						{
							j = (Newtonsoft.Json.Linq.JObject)j[jsonPropertyName];
						}
						else
						{
							j = null;
							break;
						}
					}
					if (j != null && j.HasValues)
					{
						jsonPropertyName = pp;
						res = res.Replace("#" + placeholder + "#", j[jsonPropertyName].ToString());
					}
					else
					{
						if (!pKeepUnknownPlaceholders) res = res.Replace("#" + placeholder + "#", "");

					}

				}

			}

			return res;
		}

		private static void showIntro()
		{
			Console.ForegroundColor = ConsoleColor.Blue;
			Console.WriteLine("\r\nWorld of Tanks RU server tiny stats utility");
			Console.WriteLine("version: " + Assembly.GetExecutingAssembly().GetName().Version.ToString() + "\r\n");
			Console.ResetColor();
		}

		private static void showHelp()
		{
			Console.WriteLine("Application command line syntax:\r\n");
			Console.WriteLine(@"wottinystats.exe [[-fnIn ""playerslist_filename""] [-fnOut ""results_filename""] [-template ""template_filename""] [-use_dltn [-tndomain ""domain_name""]|tnfn ""TankNames_filename""]] | -help | [-clanID #ID] [-fnOut ""results_filename""] | [-searchplayer ""player_name"" [-fnOut ""searchResult_filename""] [-template ""searchResult_line_template_filename""]] | [-dltn [-fnOut ""TankNamesResults_filename""] [-tndomain ""domainname""]]");
			Console.WriteLine("\r\n***\r\n");
			Console.WriteLine(@"-fnIn ""playerslist_filename""  : optional filename for players list (default is players.txt)");
			Console.WriteLine(@"-fnOut ""results_filename""     : optional filename for results (default is results.csv)");
			Console.WriteLine(@"-template ""template_filename"" : optional filename for results line template (documentation pending)");
			Console.WriteLine(@"-use_dltn                     : download tank names from WoT's Tank Encyclopedia to replace internal");
			Console.WriteLine(@"-tndomain ""domain_name""       : optional domain name to use for tank names download (by default is RU)");
			Console.WriteLine(@"-tnfn ""TankNames_filename""    : use tank names from the TankNames_filename to replace internal");
			Console.ForegroundColor = ConsoleColor.White;
			Console.WriteLine(@"Example:");
			Console.WriteLine(@"wottinystats.exe -fnIn playerz.txt -fnOut ""c:\program files\World of Tanks\results.txt"" -template ""tabdelimeted.txt"" -dltn");

			Console.ForegroundColor = ConsoleColor.White;
			Console.WriteLine("\r\n***\r\n");
			Console.ResetColor();

			Console.WriteLine(@"-clanID #ID                   : clan id to get players list from");
			Console.WriteLine(@"-fnOut ""results_filename""     : optional filename for clan players list results (by default clanNames.txt)");
			Console.ForegroundColor = ConsoleColor.White;
			Console.WriteLine(@"Example:");
			Console.WriteLine(@"wottinystats.exe -clanID 1 -fnOut ""players.txt""");

			Console.ForegroundColor = ConsoleColor.White;
			Console.WriteLine("\r\n***\r\n");
			Console.ResetColor();

			Console.WriteLine(@"-searchPlayer ""PlayerName""    : player name to search for");
			Console.WriteLine(@"-fnOut ""searchResult_filename"": optional filename for search results (by default searchUser.txt)");
			Console.WriteLine(@"-template ""searchResult_line_template_filename"" : optional filename for results line template (documentation pending)");
			Console.ForegroundColor = ConsoleColor.White;
			Console.WriteLine(@"Example:");
			Console.WriteLine(@"wottinystats.exe -searchPlayer zlobny -fnOut ""names.txt"" -template ""templateSearch4scan.txt""");

			Console.ForegroundColor = ConsoleColor.White;
			Console.WriteLine("\r\n***\r\n");
			Console.ResetColor();

			Console.WriteLine(@"-dltn							: download tank names from the WoT tank encyclopedia page");
			Console.WriteLine(@"-tndomain ""domainname""		: server domain name to use for download tank names (by default RU)");
			Console.WriteLine(@"-fnOut ""TankNamesResults_filename"": optional filename for tank names results (by default TankNames.txt)");
			Console.ForegroundColor = ConsoleColor.White;
			Console.WriteLine(@"Example:");
			Console.WriteLine(@"wottinystats.exe -dltn -fnOut ""tanksEU.txt"" -tndomain ""EU""");

			Console.ForegroundColor = ConsoleColor.White;
			Console.WriteLine("\r\n***\r\n");
			Console.ResetColor();

			Console.WriteLine(@"-help                           : this help");

		}
		private static void showHelpAndExit()
		{
			showHelp();
			System.Environment.Exit(-3);
		}

		private static string ReplacePlaceholders(string pTarget, Dictionary<string, string> pValues, bool pKeepUnknownPlaceholders)
		{
			string s = pTarget;

			foreach (KeyValuePair<string, string> item in pValues)
			{
				s = Regex.Replace(s, '#' + item.Key + '#', item.Value, RegexOptions.IgnoreCase);
			}

			if (!pKeepUnknownPlaceholders) s = Regex.Replace(s, @"#[^\r\n#\s]+#", "");
			return s;

		}

		static string DownloadUserStatJSON(int pUserID, ExtendedWebClient wc)
		{
			int bcnt;
			DateTime lastseen;
			DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0).ToLocalTime();

			wc.Encoding = System.Text.Encoding.UTF8;

			string profile = "";
			//construct user's profile page url and loadin it
			try
			{
				profile = wc.DownloadString(new Uri(PROFILE_URL.Replace("#uid#", pUserID.ToString())));
			}
			catch (Exception e)
			{

				profile = "";
				return "";
			}

			return profile;

		}

		static System.IO.StreamWriter OpenFNOUT(string pFNOUT)
		{
			System.IO.StreamWriter wlsw = null;
			try
			{
				wlsw = new System.IO.StreamWriter(pFNOUT, false, System.Text.Encoding.UTF8);
			}
			catch (Exception e)
			{
				Console.WriteLine("Can't open " + pFNOUT);
				Console.WriteLine("Error info: " + e.Message);
				System.Environment.Exit(-2);
				return null;
			}
			return wlsw;
		}

		static Dictionary<string,string> DownloadTankNames(ExtendedWebClient wc, string server="ru")
		{
			Dictionary<string,string> res = new Dictionary<string,string>();

            wc.Encoding = System.Text.Encoding.UTF8;
			string tankspage = wc.DownloadString(new Uri(@"http://worldoftanks."+server+"/encyclopedia/tanks/"));

			//getting match collection of all tanks names
			Regex regex = new Regex(
				"<a.+?href=\"/encyclopedia/tanks/[^/]+/([^/]+)/\"[^>]*?>([^<>]*?)</a>",
				RegexOptions.IgnoreCase
				| RegexOptions.Multiline
				| RegexOptions.CultureInvariant
				| RegexOptions.IgnorePatternWhitespace
				);

			MatchCollection ms = regex.Matches(tankspage);

			foreach (Match item in ms)
			{
                if (!res.ContainsKey(item.Groups[1].Value.ToLower())) res.Add(item.Groups[1].Value.ToLower(), item.Groups[2].Value.Trim());
			}

			return res;
		}

		private static Dictionary<string, string> LoadTankNamesFromFile(string pFileName)
		{
			string sTankKey, sTankValue;
			Dictionary<string, string> res = new Dictionary<string, string>();
			// now loading names and loading stats
			System.IO.StreamReader wlsr=null;
			try
			{
				wlsr = new System.IO.StreamReader(pFileName, System.Text.Encoding.UTF8);
			}
			catch (Exception e)
			{
				Console.WriteLine("Can't open " + pFileName);
				Console.WriteLine("Error info: " + e.Message);
				System.Environment.Exit(-1);
			}

			string s = wlsr.ReadLine();
			while (s != null)
			{
				MatchCollection mc = Regex.Matches(s, @"(.*?):(.*)");
				if (mc.Count == 0 || mc[0].Groups.Count != 3)
				{
					Console.WriteLine("Wrong WOT Tank name info provided: " + s);
					Console.WriteLine("Correct format is internal_tankname:DisplayTankName (i.e. t_50_2:Т-50-2)");
				}
				else
				{
					sTankKey = mc[0].Groups[1].Value.ToLowerInvariant().Trim();
					sTankValue = mc[0].Groups[2].Value.Trim();

					if (!res.ContainsKey(sTankKey)) res.Add(sTankKey, sTankValue);
				}
				s = wlsr.ReadLine();
			}

			return res;

		}


	}



}
